/*Algorithm.c : Implements the algorithm of this assembler application. From here we read the line from the as file and then
				call for analystic function to analyze the string, the module receives a structure that he then perform the algorithm on.
				There is the first loop of the algorithm that initializes lists and set all sort of flags of the line.
				The second loop updates the address of the symbols and prints to all the files.*/

#include "stdafx.h"
#include "LineAnalyzer.h"
#include "StringExtension.h"
#include "FileHelper.h"
#include "Algorithm.h"

#define MAX_PATH_LENGTH 256 /* Maximum path in windows is 256 characters */
#define ASSEMBLER_FILE_EXTENSION ".as" /* Legal file extension */
#define START_ADDRESS_NUMBER 100
#define BITS_NUMBER	20

/* Type of the label */
typedef enum SYMBOL_TYPE
	{
		SYMBOL_DATA,
		SYMBOL_CODE
	};

/* Absolure / Relocatable / External */
typedef enum ARE
	{
		NONE = ' ',
		ABSOLUTE = 'a',
		RELOCATABLE = 'r',
		EXTERNAL = 'e'
	};

/* Struct that represents a line in the obj file */
struct ObjLineStruct{
	char label[MAX_LINE_LENGTH]; /* Label if there is any */
	int lineNumber;				 /* Number of the line    */
	int machineCode;			 /* Machine code - relevant for command. relevant also when it is a number */	
	LineStruct* line;			 /* Pointer to the line structure */
};

/* Struct that holds definition of a label - external or internal */
struct LabelStruct{
	char label[MAX_LINE_LENGTH];	/* Label name */
	int lineNumber;					/* Number of the line    */
	ARE are;						/* Absolure / Reloccatable / External label */
	SYMBOL_TYPE symbol;				/* Is it a label for code or data? */
};


/*********************************************************************************************************************** */
/* Description: Compares two labels */
/* Input/Output: str1 - first label */
/* Input:		 str2 - second label */
/*********************************************************************************************************************** */
BOOL EqualLabel(void* str1, void* str2)
{
	if (strcmp(((LabelStruct*)str1)->label, ((LabelStruct*)str2)->label) == 0)
		return TRUE;
	return FALSE;
}


/*********************************************************************************************************************** */
/* Description: The function check if a label that was used during a certain address was defined */
/* Input: lineStruct - line structure, from here I take all necessary information regarding current address 
			labelsList - Linked list of labels
			offset - Is it first operand or second
			currentAdditionalWord - Current index of additional word */
/* Output: error type, can be an error or ERROR_NONE if all is normal */
/*********************************************************************************************************************** */
ERROR_TYPE CheckLabel(LineStruct* lineStruct, pLinkedList labelsList, int offset, int* currentAdditionalWord)
{
	int expectedOperands = COMMANDS_OPERANDS_ARRAY[lineStruct->uLine.sCommand.commandType];
	int legalOperandsPos = 0;				/* Current Operand position */
	

	if (offset >= expectedOperands)
		return ERROR_NONE;

	/* If there is only one operand then we should treat it as destination operand, therefore the position is 1 */
	if (expectedOperands == 1)
	{
		legalOperandsPos = 1;
	}

	/* If there is two operands then we should treat the operandIndex */
	if (expectedOperands == 2)
		legalOperandsPos = offset;

	ERROR_TYPE error = ERROR_NONE; /* Error to return */
	pElement labelElement;			/* Label element in the linked list that we search */

	switch (lineStruct->uLine.sCommand.addresses[legalOperandsPos])
	{
		case ADDRESS_IMMIDIATE:
			*currentAdditionalWord = *currentAdditionalWord + 1;
			break;

		/* Handle direct address */
		case ADDRESS_DIRECT:
			/* Search the label of the address in the linked list */
			labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 

			/* Return an error if the label was not found */
			if (labelElement == NULL)
				error = ERROR_ILLEGAL_LABEL_IS_NOT_DEFINED;
			else
				/* Increase current additional word by one */
				*currentAdditionalWord = *currentAdditionalWord + 1;
			break;
			
		/* Handle index address */
		case ADDRESS_INDEX:
			/* Search the label of the address in the linked list */
			labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 

			/* Return an error if the label was not found */
			if (labelElement == NULL)
				error = ERROR_ILLEGAL_LABEL_IS_NOT_DEFINED;
			else
			{
				/* Increase current additional word by one */
				*currentAdditionalWord = *currentAdditionalWord + 1;

				switch (lineStruct->uLine.sCommand.addressIndexes[legalOperandsPos])
				{
					/* Handle label index address */
					case INDEX_LABEL:
						/* Search the label of the address in the linked list */
						labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 
						/* Return an error if the label was not found */
						if (labelElement == NULL)
							error = ERROR_ILLEGAL_LABEL_IS_NOT_DEFINED;
						else
							*currentAdditionalWord = *currentAdditionalWord + 1;
						break;
				
					case INDEX_NUMBER:
						/* Increase current additional word by one */
						*currentAdditionalWord = *currentAdditionalWord + 1;
						break;
				}
			}
			break;
		}
	
	

	return error;
}

int GetNumberInBits(int number)
{
	unsigned int result = 0;
	unsigned int mask = 1;

	for (int i=0;i<BITS_NUMBER;i++)
	{
		int bit = number & mask;
		result += bit;
		mask = mask << 1;
	}
	
	return result;
}

/*********************************************************************************************************************** */
/* Description: Print error according to the error type */
/* Input: error - Error code
			lineNumber - Line that the error occured in
/*********************************************************************************************************************** */
void PrintError(ERROR_TYPE error, int lineNumber)
{
	if (error != ERROR_NONE)
	{
		char * errorStr = (char *)ERRORS_TEXT[(int)error];
		printf("Error in line number %d. Description: %s\n", lineNumber, errorStr);
	}
}

/*********************************************************************************************************************** */
/* Description: Print the additional words of the command per operand */
/* Input: commandStruct - line structure, from here I take all necessary information regarding current address 
			labelsList - Linked list of labels
			offset - Is it first operand or second
			currentAdditionalWord - Current index of additional word
			endIC - End of IC, it is relevant for data labels that we need to add end of IC to their addrees
			ic - current line number 
			file - obj file name  */
/* Output: error type, can be an error or ERROR_NONE if all is normal */
/*********************************************************************************************************************** */
void PrintCommandOperand(ObjLineStruct* commandStruct, pLinkedList labelsList, int offset, int* currentAdditionalWord, int endIC, int* ic)
{
	pElement labelElement;			/* Label element in the linked list that we search */
	LabelStruct* label;				/* Label in the linked list */
	int number;						/* Number to print */
	LineStruct* lineStruct = commandStruct->line; /* Line struct from the command */

	//LabelStruct* labelStruct = (LabelStruct*)labelsList->head;
	int expectedOperands = COMMANDS_OPERANDS_ARRAY[lineStruct->uLine.sCommand.commandType];
	int legalOperandsPos = 0;				/* Current Operand position */

	if (offset >= expectedOperands)
		return;
	
	/* If there is only one operand then we should treat it as destination operand, therefore the position is 1 */
	if (expectedOperands == 1)
		legalOperandsPos = 1;

	/* If there is two operands then we should treat the operandIndex */
	if (expectedOperands == 2)
		legalOperandsPos = offset;


	switch (lineStruct->uLine.sCommand.addresses[legalOperandsPos])
	{
		/* Handle immidiate address */
	case ADDRESS_IMMIDIATE:
		StringToInteger(lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord] , &number);

		/* Print to file */
		printObj((*ic)++, GetNumberInBits(number), ABSOLUTE);

		/* Increase current additional word by one */
		*currentAdditionalWord  += 1;
		break;
			/* Handle direct address */
	case ADDRESS_DIRECT:

		/* Search the label of the address in the linked list */
		labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 

		/* Return an error if the label was not found */
		if (labelElement == NULL)
			return;
		else
		{
			label = (LabelStruct*)labelElement->data;

			/* Handle external label usage */
			if (label->are == EXTERNAL)
			{
				printExternal(label->label, *ic);
				label->lineNumber = 0;
			}

			/* Print to file */
			printObj((*ic)++, (label->symbol == SYMBOL_DATA)? label->lineNumber + endIC : label->lineNumber, (char)label->are);
			
			/* Increase current additional word by one */
			*currentAdditionalWord += 1;
		}
		break;						
		
		/* Handle index address */
	case ADDRESS_INDEX:
	
		/* Search the label of the address in the linked list */
		labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 

		/* Return an error if the label was not found */
		if (labelElement == NULL)
			return;
		else
		{
			label = (LabelStruct*)labelElement->data;

			/* Print to file */
			printObj((*ic)++, (label->symbol == SYMBOL_DATA)? label->lineNumber + endIC : label->lineNumber, (char)label->are);

			/* Increase current additional word by one */
			*currentAdditionalWord  += 1;

			switch ((lineStruct->uLine.sCommand.addressIndexes[legalOperandsPos]))
			{
				/* Handle label index address */
				case INDEX_LABEL:
					/* Search the label of the address in the linked list */
					labelElement = Search(labelsList, lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord]); 
					/* Return an error if the label was not found */
					/* Handle external label usage */
						if (label->are == EXTERNAL)
						{
							int temp=*ic;
							printExternal(label->label, temp-1);
							label->lineNumber = 0;
						}
					
					
					if (labelElement == NULL)
						return;
					else
					{
						label = (LabelStruct*)labelElement->data;

						
						
						/* Print to file */
						printObj((*ic)++, (label->symbol == SYMBOL_DATA)? label->lineNumber + endIC - commandStruct->lineNumber : label->lineNumber - commandStruct->lineNumber, ABSOLUTE);

						*currentAdditionalWord  += 1;
					}
					break;
				case INDEX_NUMBER:
					StringToInteger(lineStruct->uLine.sCommand.additionalWords[*currentAdditionalWord] , &number);

					/* Print to file */
					printObj((*ic)++, GetNumberInBits(number), ABSOLUTE);
					
					/* Increase current additional word by one */
					*currentAdditionalWord +=  1;
					break;
			}
		}
		break;
	}
}


/*********************************************************************************************************************** */
/* Description: The function performs the main assembler operation, from here we call the LineAnalyzer and create the obj files */
/* Input: lineStruct - line structure, from here I take all necessary information regarding current address 
			filePath - file to analyze
/*********************************************************************************************************************** */
void PerformMainAssemblerOperation(char* filePath)
{
	char fullFilePath[MAX_PATH_LENGTH];			/* Assembler file path */

	/* Add ".as" extension to the file path */
	strcpy(fullFilePath, filePath);
	strcat(fullFilePath, ASSEMBLER_FILE_EXTENSION);

	/* Open the file for reading */
    FILE *file = fopen((const char*)fullFilePath , READ_MODE);

	/* Check that the file exists */
	if (file) 
	{
		LinkedList labelsList;				/* Linked list that contains all the defined labels (external and internal)*/
		LinkedList commandList;				/* Linked list that contains all the commands lines */
		LinkedList operationList;			/* Linked list that contains all the operations lines */
		LinkedList entryList;
		LineStruct* lineStruct;				/* Line structure - raw text analyzed */
		ObjLineStruct* commandStruct;		/* Command that should be in the obj file - it should be inserted to the linked list */
		ObjLineStruct* operationStruct;		/* Operation that should be in the obj  file - it should be inserted to the linked list */
		LabelStruct* labelStruct;			/* Label struct for defined label - it should be inserted to the linked list */
		BOOL isError = FALSE;				/* Is there an error? */
		ERROR_TYPE error = ERROR_NONE;		/* Error variable */
		char line [MAX_LINE_LENGTH];		/* Line to read from the file */
		int lineCounter = 0;				/* Line counter */
		int DC=0;							/* Data counter */
		int IC=START_ADDRESS_NUMBER;							/* Code counter */

		/* Initialze FileHelper global variables */
		strcpy(origFilePath, filePath);
		isEntryFileOpened = FALSE;
		isExternFileOpened = FALSE;
		isObjFileOpened = FALSE;

		/* Initialize the lists */
		labelsList.Equal = EqualLabel;
		entryList.Equal = EqualString;
		InitList(&labelsList);
		InitList(&commandList);
		InitList(&operationList);
		InitList(&entryList);
		printf("---------------------------------\n");
		printf("Starting to assemble %s\n", fullFilePath);

		/* ***************************************************************************** */
		/* First loop - Here we analyze the line and perform the logic of the first loop */
		/* ***************************************************************************** */

		while (fgets(line, sizeof line, file) != NULL ) /* read a line */
		{
			error = ERROR_NONE;
			/* Increase the line counter */
			lineCounter = lineCounter + 1;
			/* Create dynamically the line structure object */
			lineStruct = (LineStruct*) malloc(sizeof(LineStruct));
			lineStruct->lineNumber = lineCounter;

			/* Analyze the line, all the details of the line is in lineStruct */
			AnalyzeLine(line, lineStruct);

			/* Check that the line is valid */
			if (lineStruct->error == ERROR_NONE)
			{
				/* Insert the label of the line to a linked list BUT ignore labels of external and entry*/
				if ((lineStruct->label != NULL && strlen(lineStruct->label) > 0) &&
					!((lineStruct->linetype ==  OPERATION &&  
					(lineStruct->uLine.sOperation.operationType == OPERATION_EXTERN || 
					lineStruct->uLine.sOperation.operationType == OPERATION_ENTRY))))
				{
					/* Check that the label isn't defined twice and if not add it to the list */
					if (ContainsData(&labelsList, lineStruct->label))
						error = ERROR_ILLEGAL_LABEL_IS_DEFINED_MORE_THEN_ONCE;
					else
					{
						labelStruct = (LabelStruct*)malloc(sizeof(LabelStruct));
						strcpy(labelStruct->label, lineStruct->label);

						/* TODO: HANDLE LABEL OF ENTRY OR EXTERN */

						/* Set the line number and symbol type according to the line type */
						switch (lineStruct->linetype)
						{
						case OPERATION:
							labelStruct->lineNumber = DC;
							labelStruct->symbol = SYMBOL_DATA;
							break;
						case COMMAND:
							labelStruct->lineNumber = IC;
							labelStruct->symbol = SYMBOL_CODE;
							break;
						}
								
						/* Set ARE and insert to the labels list */
						labelStruct->are = RELOCATABLE;
						Insert(&labelsList, labelStruct);
					}
				}

				/* Handle error */
				if (error != ERROR_NONE)
				{
					PrintError(error, lineStruct->lineNumber);
					isError = TRUE;
					continue;
				}

				/* Insert the label of extern to a linked list */
				if (lineStruct->linetype ==  OPERATION &&  lineStruct->uLine.sOperation.operationType == OPERATION_EXTERN)
				{
					/* Check that the label isn't defined twice and if not add it to the list */
					if (ContainsData(&labelsList, lineStruct->uLine.sOperation.uData.stringData))
						error = ERROR_ILLEGAL_LABEL_IS_DEFINED_MORE_THEN_ONCE;
					else
					{
						/* Insert extern label */
						labelStruct = (LabelStruct*)malloc(sizeof(LabelStruct));
						strcpy(labelStruct->label, lineStruct->uLine.sOperation.uData.stringData);
						labelStruct->are = EXTERNAL;

						Insert(&labelsList, labelStruct);
						
					}
				}

				/* Handle error */
				if (error != ERROR_NONE)
				{
					PrintError(error, lineStruct->lineNumber);
					isError = TRUE;
					continue;
				}

				/* Handle operation */
				if (lineStruct->linetype==OPERATION)
				{
					
					switch (lineStruct->uLine.sOperation.operationType){
						/* Handle data */
						case OPERATION_DATA:
							for (int i=0;i<lineStruct->uLine.sOperation.dataLength;i++)
							{
								operationStruct = (ObjLineStruct*)malloc(sizeof(ObjLineStruct));
								operationStruct->lineNumber=DC;
								operationStruct->machineCode=lineStruct->uLine.sOperation.uData.intData[i];
								operationStruct->line = lineStruct;
								strcpy(operationStruct->label, lineStruct->label);

								Insert(&operationList, operationStruct);
								DC++;
							}
							break;
						case OPERATION_STRING:
							/* Handle string */
							for (int i=0;i<lineStruct->uLine.sOperation.dataLength;i++)
							{
								operationStruct = (ObjLineStruct*)malloc(sizeof(ObjLineStruct));
								operationStruct->lineNumber=DC;
								operationStruct->machineCode=(lineStruct->uLine.sOperation.uData.stringData[i]);
								operationStruct->line = lineStruct;
								strcpy(operationStruct->label, lineStruct->label);

								Insert(&operationList, operationStruct);
								DC++;
							}
							break;
							
							/* Handle entry */
						case OPERATION_ENTRY:
							operationStruct = (ObjLineStruct*)malloc(sizeof(ObjLineStruct));
							strcpy(operationStruct->label, lineStruct->uLine.sOperation.uData.stringData);
							
							operationStruct->line = lineStruct;
							Insert(&entryList,operationStruct->label);
							Insert(&operationList, operationStruct);
							break;
					}
				
				}
				/* Handle command */
				else if (lineStruct->linetype==COMMAND)
				{
					commandStruct = (ObjLineStruct*)malloc(sizeof(ObjLineStruct));
					commandStruct->lineNumber=IC;
					commandStruct->line = lineStruct;
					if (lineStruct->label)
						strcpy(commandStruct->label,lineStruct->label);
					
					IC=IC+lineStruct->uLine.sCommand.additionalWordsCount+1;
					Insert(&commandList, commandStruct);
				}
			
			}
			else
			{
				/* Handle error */
				error = lineStruct->error;
				PrintError(error, lineStruct->lineNumber);
			}
			
			isError |= error != ERROR_NONE;
		}

		/* Close the .as file */
		fclose(file);

		/* ********************************************************************************************** */
		/* Second loop - Here we check that all address labels are defined, perform the second loop logic */
		/* ********************************************************************************************** */
		
		/* Print the header */
		if (!isError)
			printObjHeader(IC - START_ADDRESS_NUMBER, DC);
		
		pElement current = commandList.head; /* Current element in the commands list */
		pElement labelElement;				 /* Label element that I searched */
		char* label;
		int endIC = IC;
		IC=START_ADDRESS_NUMBER;
		
		/* Go over all the list */
		while (current)
		{
			ObjLineStruct* commandStruct = (ObjLineStruct*)current->data;
			error = ERROR_NONE;
			label = NULL;
			
			/* Check usage of label */
			lineStruct = commandStruct->line;
			/* Check that the line is a command, then check for every operand that the labels that were used are well defined */
			if (lineStruct->linetype == COMMAND) 
			{
				int currentAdditionalWord = 0;
				error = CheckLabel(lineStruct, &labelsList, FIRST_OPERAND, &currentAdditionalWord);
				if (error == ERROR_NONE)
					error = CheckLabel(lineStruct, &labelsList, SECOND_OPERAND, &currentAdditionalWord);

				PrintError(error, lineStruct->lineNumber);	
			}

			isError |= error;

			/* Handle error */
			if (isError)
			{
				current = current->Next;
				continue;
			}
			
			/* Transform a command to its numeric format by bits manipulation */
			commandStruct->machineCode = produceMachineCode(&commandStruct->line->uLine.sCommand);
				/* Write to obj file */
			printObj(IC, commandStruct->machineCode, ABSOLUTE);

			/* Print to the entry file if this is a label that was declared as entry */
			pElement currentEntry;

			if ((currentEntry = Search(&entryList, commandStruct->label)) != NULL)
			{
				printEntry(commandStruct->label, IC);
				Remove(&entryList, currentEntry);
			}
			
			IC++;

			/* Print the additional words to the file */
			int currentAdditionalWord = 0;
			PrintCommandOperand(commandStruct, &labelsList, 0, &currentAdditionalWord, endIC, &IC);
			PrintCommandOperand(commandStruct, &labelsList, 1, &currentAdditionalWord, endIC, &IC);
				
			current = current->Next;
		}

		char* entriesFileName;
		pElement currentOperationElement = operationList.head;
		ERROR_TYPE operationLoopError;
		DC=0;
		/* Go over all the operations */
		while (currentOperationElement)
		{
			lineStruct =((ObjLineStruct*)currentOperationElement->data)->line;

			if (lineStruct->linetype == OPERATION && lineStruct->uLine.sOperation.operationType == OPERATION_ENTRY) 
			{
				/* Search the label of the address in the linked list */
				if (ContainsData(&labelsList, lineStruct->uLine.sOperation.uData.stringData) == FALSE)
					error = ERROR_ILLEGAL_LABEL_IS_NOT_DEFINED;
			}

			/* Handle error */
			if (error != ERROR_NONE)
			{
				PrintError(error, lineStruct->lineNumber);
				isError = TRUE;
				currentOperationElement=currentOperationElement->Next;
				continue;
			}

			if (isError)
			{
				currentOperationElement=currentOperationElement->Next;
				continue;
			}
				

			

			ObjLineStruct* currentOperationStruct = (ObjLineStruct*)currentOperationElement->data;

			/* Print to the entry file if this is a label that was declared as entry */
			pElement currentEntry;
			if ((currentEntry = Search(&entryList, currentOperationStruct->label)) != NULL)
			{
				printEntry(currentOperationStruct->label, IC);
				Remove(&entryList, currentEntry);
			}

			/* According to the operation type print the relevant line */
			switch (lineStruct->uLine.sOperation.operationType)
			{
			case OPERATION_DATA:
				currentOperationStruct->lineNumber += IC;

				/* Print the line by restricting it to 20 bits */
				printObj(currentOperationStruct->lineNumber,GetNumberInBits(currentOperationStruct->machineCode), NONE);
				DC+=currentOperationStruct->line->uLine.sOperation.dataLength;
				break;
			case OPERATION_STRING:
				currentOperationStruct->lineNumber += IC;
				/* Print the line */
				printObj(currentOperationStruct->lineNumber,currentOperationStruct->machineCode, NONE);
				DC+=currentOperationStruct->line->uLine.sOperation.dataLength;
				break;
			}
			
			currentOperationElement=currentOperationElement->Next;
		}
		/*clean up operation*/
		cleanUp(labelsList,commandList,operationList,entryList);
		free(labelStruct);
		free(commandStruct);


		if (!isError)
			printf("Assembling file %s was finished sucessfuly\n", fullFilePath);
		else
		{
			printf("Assembling file %s was finished with errors \n", fullFilePath);
			clearEntryFile();
			clearExternalFile();
			clearObjFile();
		}
	
		printf("---------------------------------\n");
	}
	else
	{
		printf("Could not open the file %s\n", fullFilePath);
	}
}

void cleanUp(LinkedList labelsList,	LinkedList commandList, LinkedList operationList, LinkedList entryList)
{
	DeleteList(&labelsList);
	DeleteList(&commandList);
	DeleteList(&operationList);
	DeleteList(&entryList);
}